package org.domeos.framework.api.service.project.impl;

import org.domeos.basemodel.HttpResponseTemp;
import org.domeos.basemodel.ResultStat;
import org.domeos.framework.api.biz.OperationHistory;
import org.domeos.framework.api.biz.global.GlobalBiz;
import org.domeos.framework.api.biz.project.ProjectBiz;
import org.domeos.framework.api.biz.resource.ResourceBiz;
import org.domeos.framework.api.consolemodel.project.CodeSourceInfo;
import org.domeos.framework.api.consolemodel.project.ProjectCreate;
import org.domeos.framework.api.consolemodel.project.ProjectList;
import org.domeos.framework.api.controller.exception.ApiException;
import org.domeos.framework.api.model.auth.User;
import org.domeos.framework.api.model.auth.related.Role;
import org.domeos.framework.api.model.ci.BuildHistory;
import org.domeos.framework.api.model.ci.CodeType;
import org.domeos.framework.api.model.operation.OperationRecord;
import org.domeos.framework.api.model.operation.OperationType;
import org.domeos.framework.api.model.project.GitLabUser;
import org.domeos.framework.api.model.project.Project;
import org.domeos.framework.api.model.project.SubversionUser;
import org.domeos.framework.api.model.project.related.CodeConfiguration;
import org.domeos.framework.api.model.project.related.CodeManager;
import org.domeos.framework.api.model.project.related.ProjectState;
import org.domeos.framework.api.model.resource.Resource;
import org.domeos.framework.api.model.resource.related.ResourceType;
import org.domeos.framework.api.service.BaseService;
import org.domeos.framework.api.service.project.ProjectService;
import org.domeos.framework.engine.AuthUtil;
import org.domeos.framework.engine.coderepo.CodeApi;
import org.domeos.framework.engine.coderepo.CodeApis;
import org.domeos.global.CurrentThreadInfo;
import org.domeos.global.GlobalConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 */
@Service("projectService")
public class ProjectServiceImpl extends BaseService implements ProjectService {

    private static Logger logger = LoggerFactory.getLogger(ProjectServiceImpl.class);

    @Autowired
    ProjectBiz projectBiz;
    @Autowired
    ResourceBiz resourceBiz;
    @Autowired
    GlobalBiz globalBiz;
    @Autowired
    OperationHistory operationHistory;

    @Override
    public HttpResponseTemp<?> create(ProjectCreate projectCreate) {

        checkUserIsLogin();

        Project project = projectCreate.getProject()
                .checkLegality()
                .withStatus(ProjectState.active);

        projectBiz.checkExistName(project.getName());

        if (project.configCodeRepository() && project.needAutoBuild()) {
            applyAutoBuildHook(project);
        }

        projectBiz.save(project);

        resourceBiz.save(project.getId(), ResourceType.PROJECT, projectCreate.getCreatorId(), projectCreate.getCreatorType(), Role.MASTER);

        operationHistory.insertRecord(new OperationRecord(project.getId(), ResourceType.PROJECT, OperationType.SET, "OK", ""));

        return ResultStat.OK.wrap(project);

    }

    private void applyAutoBuildHook(Project project) {
        CodeApi codeApi = CodeApis.createCodeApi(project.getCodeInfo());
        if(codeApi!=null){
            codeApi.applyProjectHook(globalBiz.getServer(),project);
        }
    }


    @Override
    public HttpResponseTemp<?> delete(int id) {
        checkDeletable(id);
        projectBiz.removeById(GlobalConstant.PROJECT_TABLE_NAME, id);

        resourceBiz.deleteByIdAndType(id, ResourceType.PROJECT);

        OperationRecord record = new OperationRecord(id, ResourceType.PROJECT, OperationType.DELETE,
                CurrentThreadInfo.getUserId(), CurrentThreadInfo.getUserName(), "OK", "", System.currentTimeMillis());
        operationHistory.insertRecord(record);

        return ResultStat.OK.wrap(null);
    }

    @Override
    public HttpResponseTemp<?> modify(Project project) {
        if (project == null) {
            throw ApiException.wrapMessage(ResultStat.PROJECT_NOT_LEGAL, "project info is null");
        }

        checkModifiable(project.getId());

        project.checkLegality();

        projectBiz.updateById(project);

        OperationRecord record = new OperationRecord(project.getId(), ResourceType.PROJECT, OperationType.MODIFY,
                CurrentThreadInfo.getUserId(), CurrentThreadInfo.getUserName(), "OK", "", System.currentTimeMillis());
        operationHistory.insertRecord(record);

        return ResultStat.OK.wrap(null);
    }

    @Override
    public HttpResponseTemp<Project> get(int id) {
        checkGetable(id);
        Project project = projectBiz.getById(GlobalConstant.PROJECT_TABLE_NAME, id, Project.class);
        return ResultStat.OK.wrap(project);
    }

    @Override
    public HttpResponseTemp<?> listProjectInfo() {

        User user = getLoginUser();

        List<Resource> resources = AuthUtil.getResourceList(user.getId(), ResourceType.PROJECT);

        List<Project> publicProjects = projectBiz.listAuthorized();

        List<Project> privateProjects = projectBiz.getListByReousrce(GlobalConstant.PROJECT_TABLE_NAME, resources, Project.class);

        List<ProjectList> projectListInfo = new LinkedList<>();

        for (Project project : publicProjects) {
            ProjectList projectList = generateProjectList(project);
            projectListInfo.add(projectList);
        }

        for (Project project : privateProjects) {
            if (project.getAuthority() == 0) {
                ProjectList projectList = generateProjectList(project);
                projectListInfo.add(projectList);
            }
        }

        Collections.sort(projectListInfo, new ProjectList.ProjectListComparator());

        return ResultStat.OK.wrap(projectListInfo);
    }

    private ProjectList generateProjectList(Project project) {
        BuildHistory buildHistory = projectBiz.getLatestBuildHistoryById(project.getId());
        ProjectList projectList = new ProjectList();
        projectList.setId(project.getId());
        projectList.setName(project.getName());
        if (buildHistory != null) {
            projectList.setBuildTime(buildHistory.getCreateTime());
            projectList.setBuildStatus(buildHistory.getState());
        }
        if (project.getCodeInfo() != null) {
            projectList.setCodeSshUrl(project.getCodeInfo().getCodeSshUrl());
            projectList.setCodeManager(project.getCodeInfo().getCodeManager());
            projectList.setCodeHttpUrl(project.getCodeInfo().getCodeHttpUrl());
            projectList.setNameWithNamespace(project.getCodeInfo().getNameWithNamespace());
        }
        projectList.setUserDefineDockerfile(project.isUserDefineDockerfile());
        return projectList;
    }

    @Override
    public HttpResponseTemp<?> listCodeSourceInfo() {
        User user = getLoginUser();
        CodeApi codeApi = CodeApis.createCodeApi(CodeType.getByName(CodeManager.gitlab), 0);
        if (codeApi == null) {
            throw ApiException.wrapMessage(ResultStat.PARAM_ERROR, "get code api error, please check code configuration");
        }
        List<CodeSourceInfo> codeSourceInfos = codeApi.listCodeInfo(user.getId());
        return ResultStat.OK.wrap(codeSourceInfos);
    }

    @Override
    public HttpResponseTemp<?> listSvnCodeSourceInfo() {
        User user = getLoginUser();
        CodeApi codeApi = CodeApis.createCodeApi(CodeType.getByName(CodeManager.subversion), 0);
        if (codeApi == null) {
            throw ApiException.wrapMessage(ResultStat.PARAM_ERROR, "get code api error, please check code configuration");
        }
        List<CodeSourceInfo> codeSourceInfos = codeApi.listCodeInfo(user.getId());
        return ResultStat.OK.wrap(codeSourceInfos);
    }

    @Override
    public HttpResponseTemp<?> setGitLabInfo(GitLabUser gitlab) {
        if (gitlab == null) {
            throw ApiException.wrapMessage(ResultStat.PARAM_ERROR, "git lab info is null");
        }
        User user = getLoginUser();
        gitlab.setUserId(user.getId());
        gitlab.setCreateTime(System.currentTimeMillis());
        GitLabUser oldGitlab = projectBiz.getGitLabInfoByUserIdAndName(gitlab.getUserId(), gitlab.getName());
        if (oldGitlab == null) {
            projectBiz.addGitLabInfo(gitlab);
        } else {
            gitlab.setId(oldGitlab.getId());
        }

        CodeApi codeApi = CodeApis.createCodeApi(CodeType.getByName(CodeManager.gitlab), gitlab.getId());
        if (codeApi == null) {
            throw ApiException.wrapMessage(ResultStat.GITLAB_GLOBAL_INFO_NOT_EXIST, "get code api error, please check code configuration");
        }
        CodeSourceInfo codeSourceInfo = new CodeSourceInfo(gitlab.getId(), gitlab.getName(), codeApi.getGitlabProjectInfos());

        OperationRecord record = new OperationRecord(gitlab.getId(), ResourceType.GITLAB, OperationType.SET,
                user.getId(), user.getUsername(), "OK", "", System.currentTimeMillis());
        operationHistory.insertRecord(record);

        return ResultStat.OK.wrap(codeSourceInfo);
    }

    @Override
    public HttpResponseTemp<?> setSubversionInfo(SubversionUser subversion) {
        if (subversion == null) {
            throw ApiException.wrapMessage(ResultStat.PARAM_ERROR, "svn info is not null");
        }
        User user = getLoginUser();
        subversion.setUserId(user.getId());
        subversion.setCreateTime(System.currentTimeMillis());
        SubversionUser oldSubversion = projectBiz.getSubversionInfoByUserIdAndSvnPath(user.getId(), subversion.getSvnPath());
        if (oldSubversion == null) {
            projectBiz.addSubversionInfo(subversion);
        } else {
            subversion.setId(oldSubversion.getId());
        }
        CodeApi codeApi = CodeApis.createCodeApi(CodeType.getByName(CodeManager.subversion), subversion.getId());
        if (codeApi == null) {
            throw ApiException.wrapMessage(ResultStat.PARAM_ERROR, "get code api error, please check code configuration");
        }
        CodeSourceInfo codeSourceInfo = new CodeSourceInfo(subversion.getId(), subversion.getName(),
                codeApi.getSubversionProjectInfo(subversion.getId()));

        OperationRecord record = new OperationRecord(subversion.getId(), ResourceType.SUBVERSION, OperationType.SET,
                user.getId(), user.getUsername(), "OK", "", System.currentTimeMillis());
        operationHistory.insertRecord(record);

        return ResultStat.OK.wrap(codeSourceInfo);
    }

    @Override
    public HttpResponseTemp<?> getProjectDockerFile(int projectId, String branch, String path) {
        checkGetable(projectId);

        Project project = projectBiz.getById(GlobalConstant.PROJECT_TABLE_NAME, projectId, Project.class);
        if (project == null) {
            throw ApiException.wrapResultStat(ResultStat.PROJECT_NOT_EXIST);
        }
        CodeConfiguration codeConfig = project.getCodeInfo();
        if (codeConfig == null) {
            throw ApiException.wrapResultStat(ResultStat.PROJECT_CODE_INFO_NOT_EXIST);
        }

        CodeApi codeApi = CodeApis.createCodeApi(CodeType.getByName(codeConfig.getCodeManager()),
                codeConfig.getCodeManagerUserId());
        if (codeApi == null) {
            throw ApiException.wrapMessage(ResultStat.PARAM_ERROR, "get code api error");
        }
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        String dockerfile = codeApi.getDockerFile(codeConfig.getCodeId(), branch, path);
        return ResultStat.OK.wrap(dockerfile);
    }

    @Override
    public HttpResponseTemp<?> getBranches(int projectId) {
        checkGetable(projectId);

        Project project = projectBiz.getById(GlobalConstant.PROJECT_TABLE_NAME, projectId, Project.class);
        if (project == null) {
            throw ApiException.wrapResultStat(ResultStat.PROJECT_NOT_EXIST);
        }
        CodeConfiguration codeConfig = project.getCodeInfo();
        if (codeConfig == null) {
            throw ApiException.wrapResultStat(ResultStat.PROJECT_CODE_INFO_NOT_EXIST);
        }

        CodeApi codeApi = CodeApis.createCodeApi(CodeType.getByName(codeConfig.getCodeManager()),
                codeConfig.getCodeManagerUserId());
        if (codeApi == null) {
            throw ApiException.wrapMessage(ResultStat.PARAM_ERROR, "get code api error");
        }

        List<String> branches = codeApi.getBranches(codeConfig.getCodeId());
        return ResultStat.OK.wrap(branches);
    }

    @Override
    public HttpResponseTemp<?> getReadme(int projectId, String branch) {
        checkGetable(projectId);

        Project project = projectBiz.getById(GlobalConstant.PROJECT_TABLE_NAME, projectId, Project.class);
        if (project == null) {
            throw ApiException.wrapResultStat(ResultStat.PROJECT_NOT_EXIST);
        }
        CodeConfiguration codeConfig = project.getCodeInfo();
        if (codeConfig == null) {
            throw ApiException.wrapResultStat(ResultStat.PROJECT_CODE_INFO_NOT_EXIST);
        }

        CodeApi codeApi = CodeApis.createCodeApi(CodeType.getByName(codeConfig.getCodeManager()),
                codeConfig.getCodeManagerUserId());
        if (codeApi == null) {
            throw ApiException.wrapMessage(ResultStat.PARAM_ERROR, "get code api error");
        }

        String readme = codeApi.getReadme(codeConfig.getCodeId(), branch);
        if (readme != null) {
            return ResultStat.OK.wrap(new String(readme));
        } else {
            return ResultStat.OK.wrap(null);
        }
    }

    @Override
    public HttpResponseTemp<?> getTags(int projectId) {
        checkGetable(projectId);

        Project project = projectBiz.getById(GlobalConstant.PROJECT_TABLE_NAME, projectId, Project.class);
        if (project == null) {
            throw ApiException.wrapResultStat(ResultStat.PROJECT_NOT_EXIST);
        }
        CodeConfiguration codeConfig = project.getCodeInfo();
        if (codeConfig == null) {
            throw ApiException.wrapResultStat(ResultStat.PROJECT_CODE_INFO_NOT_EXIST);
        }

        CodeApi codeApi = CodeApis.createCodeApi(CodeType.getByName(codeConfig.getCodeManager()),
                codeConfig.getCodeManagerUserId());
        if (codeApi == null) {
            throw ApiException.wrapMessage(ResultStat.PARAM_ERROR, "get code api error");
        }

        List<String> branches = codeApi.getTags(codeConfig.getCodeId());
        return ResultStat.OK.wrap(branches);
    }

    public void checkGetable(int id) {
        AuthUtil.verify(CurrentThreadInfo.getUserId(), id, ResourceType.PROJECT, OperationType.GET);
    }

    public void checkModifiable(int id) {
        AuthUtil.verify(CurrentThreadInfo.getUserId(), id, ResourceType.PROJECT, OperationType.MODIFY);
    }

    public void checkDeletable(int id) {
        AuthUtil.verify(CurrentThreadInfo.getUserId(), id, ResourceType.PROJECT, OperationType.DELETE);
    }
}
